<?php

/**
 * Perform a signed OAuth request with a GET, POST, PUT or DELETE operation.
 * 
 * @version $Id$
 * @author Marc Worrell <marc@mediamatic.nl>
 * @copyright (c) 2007 Mediamatic Lab
 * @date  Nov 20, 2007 1:41:38 PM
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

require_once dirname(__FILE__) . '/OAuthRequestSigner.php';


class OAuthRequester extends OAuthRequestSigner
{
	/**
	 * Perform the request, returns the response code, headers and body.
	 * 
	 * @param int usr_id		optional user id for which we make the request
	 * @param array curl_options
	 * @exception OAuthException when authentication not accepted
	 * @exception OAuthException when signing was not possible
	 * @return array (code=>int, headers=>array(), body=>string)
	 */
	function doRequest ( $usr_id = 0, $curl_options = array() )
	{
		$this->sign($usr_id);
		$text = $this->curl_raw($curl_options);
		return $this->curl_parse($text);	
	}


	/**
	 * Request a request token from the site belonging to consumer_key
	 * 
	 * @param string consumer_key
	 * @param int usr_id
	 * @exception OAuthException when no key could be fetched
	 * @exception OAuthException when no server with consumer_key registered
	 * @return array (authorize_uri, token)
	 */
	static function requestRequestToken ( $consumer_key, $usr_id )
	{
		OAuthRequestLogger::start();

		$store	= OAuthStore::instance();
		$r		= $store->getServer($consumer_key);
		$uri 	= $r['request_token_uri'];

		$oauth 	= new OAuthRequester($uri, 'POST');
		$oauth->sign($usr_id, $r);
		$text	= $oauth->curl_raw();

		if (empty($text))
		{
			throw new OAuthException('No answer from the server "'.$uri.'" while requesting a request token');
		}
		$data	= $oauth->curl_parse($text);
		if ($data['code'] != 200)
		{
			throw new OAuthException('Unexpected result from the server "'.$uri.'" ('.$data['code'].') while requesting a request token');
		}
		$token  = array();
		$params = explode('&', $data['body']);
		foreach ($params as $p)
		{
			@list($name, $value) = explode('=', $p, 2);
			$token[$name] = $oauth->urldecode($value);
		}
		
		if (!empty($token['oauth_token']) && !empty($token['oauth_token_secret']))
		{
			$store->addServerToken($consumer_key, 'request', $token['oauth_token'], $token['oauth_token_secret'], $usr_id);
		}
		else
		{
			throw new OAuthException('The server "'.$uri.'" did not return the oauth_token or the oauth_token_secret');
		}

		OAuthRequestLogger::flush();

		// Now we can direct a browser to the authorize_uri
		return array(
					'authorize_uri' => $r['authorize_uri'],
					'token'			=> $token['oauth_token']
				);
	}


	/**
	 * Request an access token from the site belonging to consumer_key.
	 * Before this we got an request token, now we want to exchange it for
	 * an access token.
	 * 
	 * @param string consumer_key
	 * @param string token
	 * @param int usr_id		user requesting the access token
	 * @exception OAuthException when no key could be fetched
	 * @exception OAuthException when no server with consumer_key registered
	 */
	static function requestAccessToken ( $consumer_key, $token, $usr_id )
	{
		OAuthRequestLogger::start();

		$store	= OAuthStore::instance();
		$r		= $store->getServerTokenSecrets($consumer_key, $token, 'request', $usr_id);
		$uri 	= $r['access_token_uri'];

		// Delete the server request token, this one was for one use only
		$store->deleteServerToken($consumer_key, $r['token'], 0, true);

		// Try to exchange our request token for an access token
		$oauth 	= new OAuthRequester($uri, 'POST');

		OAuthRequestLogger::setRequestObject($oauth);

		$oauth->sign($usr_id, $r);
		$text	= $oauth->curl_raw();
		if (empty($text))
		{
			throw new OAuthException('No answer from the server "'.$uri.'" while requesting a request token');
		}
		$data	= $oauth->curl_parse($text);

		if ($data['code'] != 200)
		{
			throw new OAuthException('Unexpected result from the server "'.$uri.'" ('.$data['code'].') while requesting a request token');
		}

		$token  = array();
		$params = explode('&', $data['body']);
		foreach ($params as $p)
		{
			@list($name, $value) = explode('=', $p, 2);
			$token[$name] = $oauth->urldecode($value);
		}
		
		if (!empty($token['oauth_token']) && !empty($token['oauth_token_secret']))
		{
			$store->addServerToken($consumer_key, 'access', $token['oauth_token'], $token['oauth_token_secret'], $usr_id);
		}
		else
		{
			throw new OAuthException('The server "'.$uri.'" did not return the oauth_token or the oauth_token_secret');
		}

		OAuthRequestLogger::flush();
	}



	/**
	 * Open and close a curl session passing all the options to the curl libs
	 * 
	 * @param string url the http address to fetch
	 * @exception OAuthException when temporary file for PUT operation could not be created
	 * @return string the result of the curl action
	 */
	protected function curl_raw ( $opts = array() )
	{
		if (isset($opts[CURLOPT_HTTPHEADER]))
		{
			$header = $opts[CURLOPT_HTTPHEADER];
		}
		else
		{
			$header = array();
		}

		$ch 		= curl_init();
		$method		= $this->getMethod();
		$url		= $this->getRequestUrl();
		$header[]	= $this->getAuthorizationHeader();
		$query		= $this->getQueryString();
		$body		= $this->getBody();
		
		if (!is_null($body))
		{
			if ($method != 'POST' && $method != 'PUT')
			{
				throw new OAuthException('A body can only be sent with a POST or PUT operation (requested method is '.$method.')');
			}

			// PUT and POST allow a request body
			if (!empty($query))
			{
				$url .= '?'.$query;
			}

			// Make sure that the content type of the request is ok
			$has_content_type = false;
			foreach ($header as $h)
			{
				if (strncasecmp($h, 'content-type:', 13) == 0)
				{
					$has_content_type = true;
				}
			}
			if (!$has_content_type)
			{
				$header[] = "Content-Type: application/octet-stream";
			}
			
			// When PUTting, we need to use an intermediate file (because of the curl implementation)
			if ($method == 'PUT')
			{
				/*
				if (version_compare(phpversion(), '5.2.0') >= 0)
				{
					// Use the data wrapper to create the file expected by the put method
					$put_file = fopen('data://application/octet-stream;base64,'.base64_encode($body));
				}
				*/
				
				$put_file = @tmpfile();
				if (!$put_file)
				{
					throw new OAuthException('Could not create tmpfile for PUT operation');
				}
				fwrite($put_file, $body);
				fseek($put_file, 0);

				curl_setopt($ch, CURLOPT_PUT, 		  true);
  				curl_setopt($ch, CURLOPT_INFILE, 	  $put_file);
  				curl_setopt($ch, CURLOPT_INFILESIZE,  strlen($body));
			}
			else
			{
				curl_setopt($ch, CURLOPT_POST,		  true);
				curl_setopt($ch, CURLOPT_POSTFIELDS,  $body);
  			}
		}
		else
		{
			// a 'normal' request, no body to be send
			if ($method == 'POST')
			{
				curl_setopt($ch, CURLOPT_POST, 		  true);
				curl_setopt($ch, CURLOPT_POSTFIELDS,  $query);
			}
			else
			{
				$url .= '?'.$query;
				if ($method != 'GET')
				{
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
				}
			}
		}

		curl_setopt($ch, CURLOPT_HTTPHEADER,	 $header);
		curl_setopt($ch, CURLOPT_USERAGENT,		 'anyMeta/OAuth 1.0 - ($Revision$)');
		curl_setopt($ch, CURLOPT_URL, 			 $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HEADER, 		 true);
	
		foreach ($opts as $k => $v)
		{
			curl_setopt($ch, $k, $v);
		}

		$txt = curl_exec($ch);
		curl_close($ch);
		
		if (!empty($put_file))
		{
			fclose($put_file);
		}

		// Tell the logger what we requested and what we received back
		$data = $method . " $url\n".implode("\n",$header);
		if (is_string($body))
		{
			$data .= "\n\n".$body;
		}
		else if ($method == 'POST')
		{
			$data .= "\n\n".$query;
		}

		OAuthRequestLogger::setSent($data, $body);
		OAuthRequestLogger::setReceived($txt);

		return $txt;
	}
	
	
	/**
	 * Parse an http response
	 * 
	 * @param string response the http text to parse
	 * @return array (code=>http-code, headers=>http-headers, body=>body)
	 */
	protected function curl_parse ( $response )
	{
		if (empty($response))
		{
			return array();
		}
	
		@list($headers,$body) = explode("\r\n\r\n",$response,2);
		$lines = explode("\r\n",$headers);
	
		// first line of headers is the HTTP response code 
		$http_line = array_shift($lines);
		if (preg_match('@^HTTP/[0-9]\.[0-9] +([0-9]{3})@', $http_line, $matches))
		{
			$code = $matches[1];
		}
	
		// put the rest of the headers in an array
		$headers = array();
		foreach ($lines as $l)
		{
			list($k, $v) = explode(': ', $l, 2);
			$headers[strtolower($k)] = $v;
		}
	
		return array( 'code' => $code, 'headers' => $headers, 'body' => $body);
	}

}

/* vi:set ts=4 sts=4 sw=4 binary noeol: */

?>